﻿using System;
using System.IO;
using Framework.Interface;
using log4net;
using log4net.Config;

namespace Framework
{
    public static class LogUtil
    {
        // *********************************************************************************
        // Public Logging Methods
        // *********************************************************************************

        #region Info
        public static void Info(string message)
        {
            Write(LogType.Info, Logger, null, message, null);
        }
        public static void Info(ILog logger, ISessionInfo session, string message)
        {
            Write(LogType.Info, logger, session, message, null);
        }
        #endregion

        #region InfoF
        public static void InfoF(string message, params object[] args)
        {
            Write(LogType.Info, Logger, null, string.Format(message, args), null);
        } 
        #endregion

        #region DebugIf
        public static void DebugIf(bool condition, string message)
        {
            if (condition)
            {
                Write(LogType.Debug, Logger, null, message, null);
            }
        }
        #endregion

        #region Debug
        public static void Debug(string message)
        {
            Write(LogType.Debug, Logger, null, message, null);
        }
        public static void Debug(ILog logger, ISessionInfo session, string message)
        {
            Write(LogType.Debug, logger, session, message, null);
        }
        #endregion

        #region DebugF
        public static void DebugF(string message, params object[] args)
        {
            Write(LogType.Debug, Logger, null, string.Format(message, args), null);
        }
        #endregion

        #region Error
        public static void Error(string message)
        {
            Write(LogType.Error, Logger, null, message, null);
        }
        public static void Error(Exception ex)
        {
            Write(LogType.Error, Logger, null, String.Empty, ex);
        }
        public static void Error(string message, Exception ex)
        {
            Write(LogType.Error, Logger, null, message, ex);
        }
        public static void Error(ILog logger, ISessionInfo session, string message, Exception ex)
        {
            Write(LogType.Error, logger, session, message, ex);
        }
        #endregion

        #region ErrorF
        public static void ErrorF(string message, Exception ex, params object[] args)
        {
            Write(LogType.Error, Logger, null, string.Format(message, args), ex);
        }
        #endregion

        #region Fatal
        public static void Fatal(string message)
        {
            Write(LogType.Fatal, Logger, null, message, null);
        }
        public static void Fatal(Exception ex)
        {
            Write(LogType.Fatal, Logger, null, String.Empty, ex);
        }
        public static void Fatal(string message, Exception ex)
        {
            Write(LogType.Fatal, Logger, null, message, ex);
        }
        public static void Fatal(ILog logger, ISessionInfo session, string message, Exception ex)
        {
            Write(LogType.Fatal, logger, session, message, ex);
        }
        #endregion

        #region FatalF
        public static void FatalF(string message, Exception ex, params object[] args)
        {
            Write(LogType.Fatal, Logger, null, string.Format(message, args), ex);
        }
        #endregion

        // *********************************************************************************
        // Public Code Flow Methods - Need to implement
        // *********************************************************************************

        #region In
        /// <summary>
        /// Logs debug information as flow enters a method.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        public static void In(Type type, string method)
        {
            In(type, method, null);
        }

        /// <summary>
        /// Logs debug information as flow enters a method.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        /// <param name="parameters">The parameters.</param>
        public static void In(Type type, string method, LogVariable parameters)
        {
            var message = FormatInOut(type, method, parameters, "In");

            Write(LogType.Debug, Logger, null, message, null);
        }

        /// <summary>
        /// Logs debug information as flow enters a method.
        /// </summary>
        /// <param name="logger">The logger.</param>
        /// <param name="session">The session.</param>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        public static void In(ILog logger, ISessionInfo session, Type type, string method)
        {
            In(logger, session, type, method, null);
        }

        /// <summary>
        /// Logs debug information as flow enters a method.
        /// </summary>
        /// <param name="logger">The logger.</param>
        /// <param name="session">The session.</param>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        /// <param name="parameters">The parameters.</param>
        public static void In(ILog logger, ISessionInfo session, Type type, string method, LogVariable parameters)
        {
            var message = FormatInOut(type, method, parameters, "In");

            Write(LogType.Debug, logger, session, message, null);
        }
        #endregion

        #region InOnly
        /// <summary>
        /// Logs debug information as flow enters a method.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        public static void InOnly(Type type, string method)
        {
            InOnly(type, method, null);
        }

        /// <summary>
        /// Logs debug information as flow enters a method.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        /// <param name="parameters">The parameters.</param>
        public static void InOnly(Type type, string method, LogVariable parameters)
        {
            var message = FormatInOut(type, method, parameters, "In Only");

            Write(LogType.Debug, Logger, null, message, null);
        }

        /// <summary>
        /// Logs debug information as flow enters a method.
        /// </summary>
        /// <param name="logger">The logger.</param>
        /// <param name="session">The session.</param>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        public static void InOnly(ILog logger, ISessionInfo session, Type type, string method)
        {
            InOnly(logger, session, type, method, null);
        }

        /// <summary>
        /// Logs debug information as flow enters a method.
        /// </summary>
        /// <param name="logger">The logger.</param>
        /// <param name="session">The session.</param>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        /// <param name="parameters">The parameters.</param>
        public static void InOnly(ILog logger, ISessionInfo session, Type type, string method, LogVariable parameters)
        {
            var message = FormatInOut(type, method, parameters, "In Only");

            Write(LogType.Debug, logger, session, message, null);
        }
        #endregion

        #region Out
        /// <summary>
        /// Logs debug information as flow exits a method.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        public static void Out(Type type, string method)
        {
            Out(type, method, null);
        }

        /// <summary>
        /// Logs debug information as flow exits a method.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        /// <param name="parameters">The parameters.</param>
        public static void Out(Type type, string method, LogVariable parameters)
        {
            var message = FormatInOut(type, method, parameters, "Out");

            Write(LogType.Debug, Logger, null, message, null);
        }

        /// <summary>
        /// Logs debug information as flow exits a method.
        /// </summary>
        /// <param name="logger">The logger.</param>
        /// <param name="session">The session.</param>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        public static void Out(ILog logger, ISessionInfo session, Type type, string method)
        {
            Out(logger, session, type, method, null);
        }

        /// <summary>
        /// Logs debug information as flow exits a method.
        /// </summary>
        /// <param name="logger">The logger.</param>
        /// <param name="session">The session.</param>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        /// <param name="parameters">The parameters.</param>
        public static void Out(ILog logger, ISessionInfo session, Type type, string method, LogVariable parameters)
        {
            var message = FormatInOut(type, method, parameters, "Out");

            Write(LogType.Debug, logger, session, message, null);
        }
        #endregion

        #region Variables
        /// <summary>
        /// Logs debug information as flow enters a method.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        public static void Variables(LogVariable parameters)
        {
            var message = FormatVariables(parameters);

            Write(LogType.Debug, Logger, null, message, null);
        }
        #endregion


        // In
        // Out
        // Before
        // After
        // Parameters

        // *********************************************************************************
        // Private Log Writers
        // *********************************************************************************

        #region Write
        private static void Write(LogType logType, ILog logger, ISessionInfo session, string message, Exception ex)
        {
            // Check we have a logger and that it is enabled for the desired LogType
            if (
                logger == null
                || (logType == LogType.Debug && !logger.IsDebugEnabled)
                || (logType == LogType.Info && !logger.IsInfoEnabled)
                || (logType == LogType.Warning && !logger.IsWarnEnabled)
                || (logType == LogType.Error && !logger.IsErrorEnabled)
                || (logType == LogType.Fatal && !logger.IsFatalEnabled)
                )
            {
                return;
            }

            // Future: In this section we will format any of the incomming paramaters in the message string if needed
            // It may be that in future we will have a complex object (XML) that is pushed through to the database and 
            // then split up into component parts. Eg. Message, Exception, Parameters, Session Information, Environment Information


            // Log to one of the five LogTypes
            switch (logType)
            {
                case LogType.Debug:
                    logger.Debug(message);
                    break;
                case LogType.Info:
                    logger.Info(message);
                    break;
                case LogType.Warning:
                    logger.Warn(message);
                    break;
                case LogType.Error:
                    logger.Error(message, ex);
                    break;
                case LogType.Fatal:
                    logger.Fatal(message, ex);
                    break;
                default:
                    logger.Debug(message);
                    break;
            }
        }
        #endregion

        // *********************************************************************************
        // Private Log Formatters
        // *********************************************************************************

        #region FormatInOut
        /// <summary>
        /// Formats the In/Out logging data.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="method">The method.</param>
        /// <param name="variables">The parameters or varialbles.</param>
        /// <param name="directionInOut">The direction, sample values 'In' 'Out'.</param>
        /// <remarks>The WriteLines are before instead of after lines, as the whole buffer needs to finish without a CR/LF, this is because the last CR/LF is handled by the logging provider if required</remarks>
        /// <returns>Formated In/Out information for logging</returns>
        private static string FormatInOut(Type type, string method, LogVariable variables, string directionInOut)
        {
            var result = new TextStringBuilder();

            try
            {
                if (type != null)
                {
                    result.Write(type.FullName);
                    result.Write(".");
                }

                result.Write("{0} - {1}", method, directionInOut);

                if (variables != null)
                {
                    foreach (var parameter in variables.Variables)
                    {
                        result.WriteLine();
                        result.WriteNameValue(parameter.Key, parameter.Value == null ? string.Empty : parameter.Value.ToString());
                    }
                }
            }
            catch (Exception ex)
            {
                result.WriteLine("LogUtil.{0} failure\n{1}", directionInOut, ExceptionUtil.Format(ex));
            }

            return result.ToString();
        }
        #endregion

        #region FormatVariables
        /// <summary>
        /// Format variables
        /// </summary>
        /// <param name="variables">The parameters or varialbles.</param>
        /// <returns>Formated variables for logging</returns>
        private static string FormatVariables(LogVariable variables)
        {
            var result = new TextStringBuilder();

            FormatVariables(result, variables);

            return result.ToString();
        }

        /// <summary>
        /// Format variables
        /// </summary>
        /// <param name="builder">The builder.</param>
        /// <param name="variables">The parameters or varialbles.</param>
        private static void FormatVariables(TextBuilder builder, LogVariable variables)
        {
            if (builder == null)
            {
                throw new ArgumentNullException("builder");
            }

            if (variables == null)
            {
                return;
            }

            foreach (var parameter in variables.Variables)
            {
                builder.WriteLine();
                builder.WriteNameValue(parameter.Key, parameter.Value == null ? string.Empty : parameter.Value.ToString());
            }
        }
        #endregion

        // *********************************************************************************
        // Initialization / Configuration
        // *********************************************************************************

        private static ILog Logger { get; set; }

        #region Configure
        private static bool IsConfigured { get; set; }

        public static void Configure(string fileName)
        {
            var configFile = FileUtil.PathCombine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, fileName);

            ConfigureFullFileName(configFile);
        }

        public static void Configure()
        {
            ConfigureFullFileName(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
        }

        private static void ConfigureFullFileName(string configFile)
        {
            if (!IsConfigured)
            {
                IsConfigured = true;

                if (File.Exists(configFile))
                {
                    XmlConfigurator.ConfigureAndWatch(new FileInfo(configFile));
                }

                Logger = LogManager.GetLogger("Logger");
            }
        }
        #endregion
    }
}
